{-# OPTIONS_GHC -XScopedTypeVariables -XTypeSynonymInstances #-}
-----------------------------------------------------------------------------
--
-- Module      :  GhCalcUtils
-- Copyright   :  (c) Lakshmi Narasimhan
-- License     :  GNU-GPL
--
-- Maintainer  :  <lakshminaras2002 at gmail dot com>
-- Stability   :  provisional
-- Portability :  portable
--
--
--
---------------------------------------------------------------------------------

module GhCalcUtils where

import System.Directory
import System.IO
import System.Posix.IO
import Data.Char
import System.Random
import Data.List
import System.Exit
import Paths_ghcalc

data Optype = Unary | Binary

opBinaryList = ["+","-","*","/","^","%"]
opUnaryList = ["log","ln", "cos","sin","euler","$"]

constants = [("e", " ")]

evaluate_postfix ::[String] -> String
evaluate_postfix [] = "0"
evaluate_postfix postfix_list = evaluate_postfix_helper postfix_list []

evaluate_postfix_helper :: [String]-> [String]-> String
evaluate_postfix_helper [] stack = last stack
evaluate_postfix_helper (x:postfix_expr) stack =
-- Lazy evaluation at work, the following expressions are not evaluated unless
-- they are used, and hence we can safely make these bindings.
    let last_stack = last (stack)
        last_stack_minus_1 = last (take (length stack -1) stack)
    in
       if( isOp x )
       then
          case opType x of
            Unary -> let result = do_unary_op x last_stack
                     in
                      evaluate_postfix_helper postfix_expr ((pop_stack stack 1) ++   (result:[]))
            Binary -> let result = do_op x last_stack_minus_1 last_stack
                      in
                       evaluate_postfix_helper postfix_expr ((pop_stack stack 2) ++ (result:[]))
       else
          evaluate_postfix_helper postfix_expr (stack ++ (x:[]))

opType :: String -> Optype
opType x = if (x `elem` opBinaryList)
           then
              Binary
           else
              Unary

do_op :: String -> String -> String -> String
do_op op op1 op2 = let
        num1 = (read op1)::Double
        num2 = (read op2)::Double
       in
    	case op of
            "+" -> show ( (+) num1  num2 )
            "-" -> show ( num1 - num2 )
            "*" -> show ( num1 * num2 )
            "/" -> show (num1 / num2)
            "^" -> do_op_exp op1 op2
            "%" -> do_op_mod op1 op2

do_op_exp op1 op2 = let
      num1 = (read op1)::Double
      num2 = (read (takeWhile (/='.') op2))::Integer -- reading 0.xxx as Integer causes no parse :)
     in
       show (num1 ^ num2)

do_op_mod op1 op2 = let
                num1 =  (read (takeWhile (/='.') op1))::Integer
                num2 =  (read (takeWhile (/='.') op2))::Integer
               in
                  show (num1 `mod` num2)

do_unary_op op op1 = let
    num1 = (read op1)::Double
   in
    case op of
      "log" -> show (logBase 10 num1)
      "ln" -> show (log num1)
      "euler" -> show (exp num1)
      "$" -> show (0-num1)

isOp :: String -> Bool
isOp [] = False
isOp x = (x `elem` opBinaryList) || (x `elem` opUnaryList)

pop_stack :: [String] -> Int -> [String]
pop_stack [] _ = []
pop_stack stack count = take (length stack - count) stack

digitToDouble :: Char -> Double
digitToDouble x = case x of
		  '1' -> 1.0
		  '2' -> 2.0
		  '3' -> 3.0
		  '4' -> 4.0
		  '5' -> 5.0
		  '6' -> 6.0
		  '7' -> 7.0
		  '8' -> 8.0
		  '9' -> 9.0
		  '0' -> 0.0
		  otherwise -> 0.0

getRandomString :: IO String
getRandomString = do
    let listLen = length messageList
    let rand =  getStdRandom (randomR (0, (listLen-1)))
    index <- rand
    return (messageList !! index)

messageList = ["hello world"]

-- TODO In all the code that follows, handle exceptions
-- TODO Put platform independent path separator
getConfigDir :: IO FilePath
getConfigDir = do
  homeDirPath <- getHomeDirectory
  let configDirPath = homeDirPath ++ "/.ghcalc"
  createDirectoryIfMissing False configDirPath
  return configDirPath

getSessionFileName :: IO String
getSessionFileName = do
  -- Check the existence of your config directory
  configDirPath <- getConfigDir
  let sessionFilePath = configDirPath ++ "/session.txt"
  handle <- openFile sessionFilePath AppendMode
  hClose handle
  return sessionFilePath

getGladeFilePath :: IO String
getGladeFilePath = do
    -- Check the existence of bin directory
    dataDir <- getDataDir
    putStrLn dataDir
    return (dataDir ++ "/data/ghcalc.glade")

-- I want IO Bool, but openFd fails anyway. Best approach is to use exceptions
-- and propagate values of known type to the enclosing expression
createLockFile :: IO Bool
createLockFile = do
  configDirPath <- getConfigDir
  let lockFilePath = configDirPath ++ "/lock"
-- This API is clumsy. I do not understand the Maybe type's purpose here
-- Using (Just 0) did not work. Had to use (Just 1). But what is Nothing
-- being used for anyway.
  fd <- openFd lockFilePath WriteOnly (Just 0o755) (OpenFileFlags False False False False False)
  catch (do
             setLock fd (WriteLock, AbsoluteSeek, 0,0)
             return True) lockExceptionHandler

lockExceptionHandler :: IOError -> IO Bool
lockExceptionHandler e = do
	putStrLn "Instance already running"
	return False
